package com.ikingtech.platform.service.application.controller;

import com.ikingtech.framework.sdk.context.exception.DownloadException;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.datasource.api.DatasourceApi;
import com.ikingtech.framework.sdk.datasource.model.DatasourceTableDTO;
import com.ikingtech.framework.sdk.enums.application.ApplicationModelTypeEnum;
import com.ikingtech.framework.sdk.enums.common.TableFieldTypeEnum;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.framework.sdk.web.annotation.PostRequest;
import com.ikingtech.platform.service.application.entity.ApplicationModelDO;
import com.ikingtech.platform.service.application.entity.ApplicationModelFieldDO;
import com.ikingtech.platform.service.application.entity.ApplicationModelRelationDO;
import com.ikingtech.platform.service.application.exception.ApplicationExceptionInfo;
import com.ikingtech.platform.service.application.service.ApplicationModelFieldService;
import com.ikingtech.platform.service.application.service.ApplicationModelRelationService;
import com.ikingtech.platform.service.application.service.ApplicationModelService;
import com.ikingtech.platform.service.application.service.generater.ApplicationModelCodeGenerator;
import com.ikingtech.framework.sdk.application.model.*;
import io.swagger.v3.oas.annotations.Parameter;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import java.io.IOException;
import java.util.List;
import java.util.Map;


/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/application/model", name = "应用中心-业务模型管理", description = "低代码中心-业务模型管理")
public class ApplicationModelController {

    private final ApplicationModelService service;

    private final ApplicationModelFieldService fieldService;

    private final ApplicationModelRelationService modelRelationService;

    private final DatasourceApi datasourceApi;

    @PostRequest(order = 1, value = "/add", summary = "新增", description = "新增。")
    public R<Object> add(@Parameter(name = "model", description = "业务模型信息")
                         @RequestBody ApplicationModelDTO model) {
        ApplicationModelDO entity = Tools.Bean.copy(model, ApplicationModelDO.class);
        entity.setId(Tools.Id.uuid());
        entity.setType(ApplicationModelTypeEnum.MASTER.name());
        entity.setAppCode(Me.appCode());
        this.insertField(entity.getId(), model.getFields());
        this.insertModelRelation(entity.getId(), model.getSlaveModels());
        this.service.save(entity);
        return R.ok(entity.getId());
    }

    private void insertModelRelation(String modelId, List<ApplicationModelRelationDTO> modelRelations) {
        this.modelRelationService.saveBatch(Tools.Coll.convertList(modelRelations, modelRelation -> {
            ApplicationModelRelationDO entity = Tools.Bean.copy(modelRelation, ApplicationModelRelationDO.class);
            entity.setId(Tools.Id.uuid());
            entity.setMasterModelId(modelId);
            return entity;
        }));
    }

    private void insertField(String modelId, List<ApplicationModelFieldDTO> fields) {
        if (Tools.Coll.isNotBlank(fields)) {
            this.fieldService.saveBatch(Tools.Coll.convertList(fields, field -> {
                ApplicationModelFieldDO entity = Tools.Bean.copy(field, ApplicationModelFieldDO.class);
                entity.setId(Tools.Id.uuid());
                entity.setModelId(modelId);
                return entity;
            }));
        }
    }

    @PostRequest(order = 2, value = "/delete", summary = "删除", description = "删除。")
    public R<Object> delete(@Parameter(name = "id", description = "业务模型编号")
                            @RequestBody String id) {
        this.service.removeById(id);
        this.fieldService.removeByModelId(id);
        this.modelRelationService.removeByMasterModelId(id);
        this.modelRelationService.removeByModelId(id);
        return R.ok();
    }

    @PostRequest(order = 3, value = "/update", summary = "更新", description = "更新。")
    public R<Object> update(@Parameter(name = "model", description = "业务模型信息")
                            @RequestBody ApplicationModelDTO model) {
        if (!this.service.exist(model.getId())) {
            throw new FrameworkException(ApplicationExceptionInfo.MODEL_NOT_FOUND);
        }
        this.updateField(model.getId(), model.getFields());
        this.updateModelRelation(model.getId(), model.getSlaveModels());
        this.service.updateById(Tools.Bean.copy(model, ApplicationModelDO.class));
        return R.ok();
    }

    private void updateModelRelation(String modelId, List<ApplicationModelRelationDTO> fields) {
        this.modelRelationService.removeByMasterModelId(modelId);
        this.insertModelRelation(modelId, fields);
    }

    private void updateField(String modelId, List<ApplicationModelFieldDTO> fields) {
        this.fieldService.removeByModelId(modelId);
        this.insertField(modelId, fields);
    }

    @PostRequest(order = 4, value = "/list/page", summary = "分页查询", description = "分页查询。")
    public R<List<ApplicationModelDTO>> page(@Parameter(name = "queryParam", description = "查询参数")
                                             @RequestBody ApplicationQueryParamDTO queryParam) {
        return R.ok(this.service.listPage(queryParam).convertBatch(this.service::modelConvert));
    }

    @PostRequest(order = 5, value = "/list/all", summary = "全量查询", description = "全量查询。")
    public R<Object> all(@Parameter(name = "showSlaveModelField", description = "查询参数")
                         @RequestParam(value = "showSlaveModelField", required = false) Boolean showSlaveModelField) {
        return R.ok(this.service.modelConvert(this.service.list(), showSlaveModelField));
    }

    @PostRequest(order = 6, value = "/detail", summary = "详情", description = "详情。")
    public R<ApplicationModelDTO> detail(@Parameter(name = "id", description = "业务模型编号")
                                         @RequestBody String id) {
        ApplicationModelDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(ApplicationExceptionInfo.MODEL_NOT_FOUND);
        }

        List<ApplicationModelRelationDO> modelRelationEntities = this.modelRelationService.listByMasterModelId(id);
        List<ApplicationModelDO> slaveModelEntities = this.service.listByIds(Tools.Coll.convertList(modelRelationEntities, ApplicationModelRelationDO::getModelId));
        Map<String, ApplicationModelDO> slaveModelMap = Tools.Coll.convertMap(slaveModelEntities, ApplicationModelDO::getId);

        return R.ok(this.service.modelConvert(entity, this.fieldService.listByModelId(entity.getId()), modelRelationEntities, slaveModelMap));
    }

    @PostRequest(order = 7, value = "/field/list/id", summary = "查询模型字段信息(模型编号)", description = "查询模型字段信息(模型编号)。")
    public R<List<ApplicationModelFieldDTO>> listFieldById(@Parameter(name = "model", description = "业务模型信息")
                                                           @RequestBody String id) {
        return R.ok(this.fieldService.modelConvert(this.fieldService.listByModelId(id)));
    }

    @PostRequest(order = 7, value = "/list/datasource-id", summary = "查询模型信息(数据源)", description = "查询模型信息(数据源)。")
    R<List<ApplicationModelDTO>> listByDatasourceId(@Parameter(name = "model", description = "业务模型信息")
                                                    @RequestBody String datasourceId) {
        List<DatasourceTableDTO> tables = this.datasourceApi.listTableById(datasourceId).getData();
        return R.ok(Tools.Coll.convertList(tables, table -> {
            ApplicationModelDTO model = new ApplicationModelDTO();
            model.setName(table.getDescription());
            model.setCode(Tools.Str.toLowerCamelCase(table.getName()));
            model.setRemark(table.getDescription());
            model.setFields(Tools.Coll.convertList(table.getFields(),
                    tableField -> !"id".equals(tableField.getName()) &&
                            !"create_by".equals(tableField.getName()) &&
                            !"create_name".equals(tableField.getName()) &&
                            !"create_time".equals(tableField.getName()) &&
                            !"update_by".equals(tableField.getName()) &&
                            !"update_name".equals(tableField.getName()) &&
                            !"update_time".equals(tableField.getName()),
                    tableField -> {
                        ApplicationModelFieldDTO modelField = new ApplicationModelFieldDTO();
                        modelField.setName(Tools.Str.toLowerCamelCase(tableField.getName()));
                        modelField.setRemark(tableField.getComment());
                        modelField.setDbTableField(true);
                        modelField.setDbTableFieldType(TableFieldTypeEnum.valueOfDbType(tableField.getType().toLowerCase()));
                        modelField.setType(modelField.getDbTableFieldType().javaType);
                        modelField.setLength(String.valueOf(tableField.getLength()));
                        modelField.setNullable(tableField.getNullable());
                        modelField.setSyncWithDatasource(true);
                        return modelField;
                    }));
            return model;
        }));
    }

    @PostRequest(order = 8, value = "/create-table-sql/preview", summary = "预览建表SQL", description = "预览建表SQL。")
    public R<String> previewCreateTableSql(@Parameter(name = "model", description = "业务模型信息")
                                           @RequestBody ApplicationModelDTO model) {
        if (Tools.Coll.isBlank(model.getFields())) {
            throw new FrameworkException(ApplicationExceptionInfo.MODEL_FIELD_NOT_SPECIFIED);
        }
        List<ApplicationModelFieldDTO> dbTableFields = Tools.Coll.filter(model.getFields(), field -> Boolean.TRUE.equals(field.getDbTableField()));
        if (Tools.Coll.isBlank(dbTableFields)) {
            throw new FrameworkException(ApplicationExceptionInfo.DB_TABLE_FIELD_NOT_FOUND);
        }
        String createTableSqlTemplate = """
                CREATE TABLE {} (
                  id char(32) NOT NULL COMMENT '主键/岗位编号',
                {}  create_by char(32) DEFAULT '' COMMENT '创建人编号',
                  create_name varchar(64) DEFAULT '' COMMENT '创建人姓名',
                  create_time datetime DEFAULT NULL COMMENT '创建时间',
                  update_by char(32) DEFAULT '' COMMENT '更新人编号',
                  update_name varchar(64) DEFAULT '' COMMENT '更新人姓名',
                  update_time datetime DEFAULT NULL COMMENT '更新时间',
                  PRIMARY KEY (id)
                ) COMMENT='{}';""";
        StringBuilder fieldSql = new StringBuilder();
        model.getFields().forEach(field -> fieldSql.append(field.getDbTableFieldType().sqlFormat(Tools.Str.toSnakeCase(field.getName()), field.getLength(), field.getRemark())));
        return R.ok(Tools.Str.format(createTableSqlTemplate, model.getCode(), fieldSql.toString(), model.getRemark()));
    }

    @PostRequest(order = 9, value = "/code/gen", summary = "生成代码", description = "生成代码。")
    public void generateCode(@Parameter(name = "id", description = "模型编号")
                             @RequestBody ApplicationCodeGenParamDTO genParam,
                             HttpServletResponse response) {
        ServletOutputStream outputStream;
        try {
            outputStream = response.getOutputStream();
        } catch (IOException e) {
            log.error("获取response输出流失败[{}]", e.getMessage());
            throw new DownloadException(ApplicationExceptionInfo.MODEL_CODE_GEN_FAIL);
        }
        List<ApplicationModelDO> entities = this.service.listByIds(genParam.getModelIds());
        if (Tools.Coll.isBlank(entities)) {
            throw new DownloadException(ApplicationExceptionInfo.MODEL_NOT_FOUND);
        }
        response.setContentType("application/octet-stream; charset=UTF-8");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, Tools.Str.format("attachment; filename={}", "code_gen.zip"));

        Map<String, String> codes = new ApplicationModelCodeGenerator(this.service, this.fieldService,
                this.modelRelationService).generate(Tools.Str.EMPTY, genParam.getPackageName(), entities);
        Tools.Zip zipInstance = Tools.Zip.instance();
        codes.forEach(zipInstance::entry);
        zipInstance.write(outputStream);
    }
}
